package main

import (
	"fmt"
	"os"
	"os/exec"
	"flag"
	"strings"
	"time"
	"strconv"
	"path/filepath"
	"io/ioutil"
)

type Cmd struct {
	target string
}

func parseCmd() *Cmd {
	cmd := &Cmd{}
	flag.Parse()

	args := flag.Args()
	if len(args) < 1 {
		printUsage()
		os.Exit(-1)
	}
	if len(args) > 0 {
		cmd.target = args[0]
	}
	return cmd
}

func printUsage() {
	fmt.Printf("Usage: %s target\n", os.Args[0])
}

/**
 * usage gitpick target
 * target 文件要复制到的目录，目录必须为空
 */
func main() {

	cmd := parseCmd()
	target := cmd.target
	file, err := os.Stat(target)
	if err == nil && file.IsDir() {
		fmt.Printf("target %s already exists\n", target)
		//return
	}

	command := "git"
	buf, err := exeCmd(command, "status")
	if err != nil {
		fmt.Println("error: ", err)
		return
	}
	lines := parseOutputLine(buf)
	fileMap := getFiles(&lines)

	currentDir := getCurrentDir()
	projectName := findProjectName(currentDir)
	projectRootDir := findProjectRootDir(currentDir) + "/" + projectName
	target = check(cmd.target, projectName)

	for key := range fileMap {
		list := fileMap[key]
		copy(key, projectRootDir, target, list)
	}
}

func copy(key, projectRootDir, target string, list []string) {
	for _, path := range list {
		switch key {
		case "untrack":
			copyFile(projectRootDir, path, target)
			break
		case "delete":
			break
		case "rename":
			break
		case "newfile":
			break
		}
		fmt.Println(key + ":" + path)
	}
}

func copyFile(projectRootDir, source, target string) {
	targetDir := target + "/" + source
	sourcePath := projectRootDir + "/" + source
	file, err := os.Stat(targetDir)
	// 已存在
	if err == nil && file.IsDir(){

	} else {
		walkFn := func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			file := strings.Replace(path, projectRootDir, target, 1)
			if info.IsDir() {
				os.MkdirAll(file, 0777)
			} else {
				buf,err := ioutil.ReadFile(path)
				if err == nil {
					ioutil.WriteFile(file, buf, 0777)
				}
			}
			return nil
		}

		filepath.Walk(sourcePath, walkFn)
	}
}

func exeCmd(cmdStr string, args string ) ([]byte, error)  {
	cmd := exec.Command(cmdStr, args)
	buf,err := cmd.Output()
	return buf, err
}

func parseOutputLine(buf []byte) []string {

	lines := *&[]string{}
	start := 0
	for i, b := range buf {
		if b == 10 {
			line := buf[start: i]
			start = i + 1
			lines = append(lines, string(line))
		}
	}
	return lines
}

func getFiles(lines *[]string) map[string][]string {

	fileMap := make(map[string][]string)
	deletedList := *&[]string{}
	renamedList := *&[]string{}
	newfileList := *&[]string{}
	untrackedList := *&[]string{}

	begin := false
	unCommitOrUnTracked := ""
	skipCount := 0
	for _, line := range *lines {

		if strings.HasPrefix(line, "Changes to be committed") {
			unCommitOrUnTracked = "uncommited"
			skipCount = 1
			continue
		}
		if strings.HasPrefix(line, "Untracked files") {
			skipCount = 1
			unCommitOrUnTracked = "untracked"
			continue
		}

		if !begin && strings.Compare(unCommitOrUnTracked, "") != 0 && skipCount < 2 {
			skipCount++
			continue
		}

		if strings.Compare(line, "") == 0 {
			begin = !begin
			skipCount = 0
			continue
		}

		if strings.Compare(line, "") == 0 {
			begin = !begin
			skipCount = 0
			continue
		}

		if begin && unCommitOrUnTracked == "uncommited" {
			line = strings.TrimSpace(line)
			if strings.HasPrefix(line,"deleted") {
				line = line[12:]
				deletedList = append(deletedList, line)
			} else if strings.HasPrefix(line,"renamed") {
				line = line[12:]
				renamedList = append(renamedList, line)
			} else if strings.HasPrefix(line,"new file") {
				line = line[12:]
				line = strings.TrimSpace(line)
				newfileList = append(newfileList, line)
			}
		} else if begin && unCommitOrUnTracked == "untracked" {
			line = line[1:]
			untrackedList = append(untrackedList, line)
		}
	}

	fileMap["delete"] = deletedList
	fileMap["rename"] = renamedList
	fileMap["newfile"] = newfileList
	fileMap["untrack"] = untrackedList
	return fileMap
}

func findProjectName(currDir string) string {

	gitDb := currDir + "/" + ".git"
	file,err := os.Stat(gitDb)
	if err == nil && file.IsDir() {
		return currDir[strings.LastIndex(currDir, "/")+1:]
	} else if "/" == currDir {
		fmt.Println("can't find .git dir")
		os.Exit(-1)
	} else {
		return findProjectName(currDir[:strings.LastIndex(currDir, "/")])
	}
	return ""
}

func findProjectRootDir(currDir string) string {
	gitDb := currDir + "/" + ".git"
	file,err := os.Stat(gitDb)
	if err == nil && file.IsDir() {
		return currDir[:strings.LastIndex(currDir, "/")]
	} else if "/" == currDir {
		fmt.Println("can't find .git dir")
		os.Exit(-1)
	} else {
		return findProjectRootDir(currDir[:strings.LastIndex(currDir, "/")])
	}
	return ""
}

func getCurrentDir() string {
	cmd := exec.Command("pwd")
	buf,_ := cmd.Output()
	output := string(buf[:len(buf)-1])
	return output
}

func check(target, projectName string) string {
	dir := target + "/" + projectName
	_,err := os.Stat(dir)
	if err == nil{
		dir = target + "/" + projectName + "_" + strconv.Itoa(time.Now().Nanosecond())
	}
	os.MkdirAll(dir, 0777)
	return dir;
}

